package com.dbflow5.processor;

import com.dbflow5.processor.definition.DatabaseDefinition;
import com.dbflow5.processor.definition.ModelViewDefinition;
import com.dbflow5.processor.definition.OneToManyDefinition;
import com.dbflow5.processor.definition.TableDefinition;
import com.dbflow5.processor.definition.TypeConverterDefinition;
import com.dbflow5.processor.definition.column.ColumnAccessor;
import com.dbflow5.processor.definition.column.ColumnDefinition;
import com.dbflow5.processor.definition.column.ReferenceColumnDefinition;
import com.dbflow5.processor.definition.provider.ContentProviderDefinition;
import com.dbflow5.processor.definition.provider.TableEndpointDefinition;
import com.squareup.javapoet.TypeName;

public class Validators {

    /**
     * Description: the base interface for validating annotations.
     */
    interface Validator<ValidatorDefinition> {

        /**
         * if the validation is passed
         *
         * @param processorManager    The manager
         * @param validatorDefinition The validator to use
         * @return true if validation passed, false if there was an error.
         */
        boolean validate(ProcessorManager processorManager, ValidatorDefinition validatorDefinition);
    }


    /**
     * Description: Ensures the integrity of the annotation com.dbflow5.processor for columns.
     *
     * @author Andrew Grosner (fuzz)
     */
    public static class ColumnValidator implements Validator<ColumnDefinition> {

        private ColumnDefinition autoIncrementingPrimaryKey = null;

        @Override
        public boolean validate(ProcessorManager processorManager, ColumnDefinition validatorDefinition) {

            boolean success = true;

            // validate getter and setters.
            if (validatorDefinition.columnAccessor instanceof ColumnAccessor.PrivateScopeColumnAccessor) {
                ColumnAccessor.PrivateScopeColumnAccessor privateColumnAccess = (ColumnAccessor.PrivateScopeColumnAccessor) validatorDefinition.columnAccessor;
                if (!validatorDefinition.entityDefinition.classElementLookUpMap.containsKey(privateColumnAccess.getterNameElement())) {
                    success = false;
                }
                if (!validatorDefinition.entityDefinition.classElementLookUpMap.containsKey(privateColumnAccess.setterNameElement())) {
                    success = false;
                }
            }

            if (!validatorDefinition.defaultValue.isEmpty()) {
                TypeName typeName = validatorDefinition.elementTypeName;
                if (validatorDefinition instanceof ReferenceColumnDefinition && ((ReferenceColumnDefinition) validatorDefinition).isReferencingTableObject) {
                    processorManager.logError(ColumnValidator.class, "Default values cannot be specified for model fields");
                } else if (typeName != null && typeName.isPrimitive()) {
                    processorManager.logWarning(ColumnValidator.class,
                            "Default value of ${validatorDefinition.defaultValue} from" +
                                    " ${validatorDefinition.entityDefinition.elementName}.${validatorDefinition.elementName}" +
                                    " is ignored for primitive columns.");
                }
            }

            if (validatorDefinition.columnName.isEmpty()) {
                success = false;
                processorManager.logError("Field ${validatorDefinition.elementName} " +
                        "cannot have a null column name for column: ${validatorDefinition.columnName}" +
                        " and type: ${validatorDefinition.elementTypeName}");
            }

            if (validatorDefinition.columnAccessor instanceof ColumnAccessor.EnumColumnAccessor) {
                if (validatorDefinition.type instanceof ColumnDefinition.Type.Primary) {
                    success = false;
                    processorManager.logError("Enums cannot be primary keys. Column: ${validatorDefinition.columnName}" +
                            " and type: ${validatorDefinition.elementTypeName}");
                } else if (validatorDefinition instanceof ReferenceColumnDefinition) {
                    success = false;
                    processorManager.logError("Enums cannot be foreign keys. Column: ${validatorDefinition.columnName}" +
                            " and type: ${validatorDefinition.elementTypeName}");
                }
            }

            if (validatorDefinition instanceof ReferenceColumnDefinition) {
                if (validatorDefinition.column != null) {
                    if (!validatorDefinition.column.name().isEmpty()) {
                        success = false;
                        processorManager.logError("Foreign Key ${validatorDefinition.elementName} cannot specify the @Column.name() field. "
                                + "Use a @ForeignKeyReference(columnName = {NAME} instead. " +
                                "Column: ${validatorDefinition.columnName} and type: ${validatorDefinition.elementTypeName}");
                    }
                }

                // it is an error to specify both a not null and provide explicit references.
                if (((ReferenceColumnDefinition) validatorDefinition).explicitReferences && validatorDefinition.notNull) {
                    success = false;
                    processorManager.logError("Foreign Key ${validatorDefinition.elementName} " +
                            "cannot specify both @NotNull and references. Remove the top-level @NotNull " +
                            "and use the contained 'notNull' field " +
                            "in each reference to control its SQL notnull conflicts.");
                }

            } else {
                if (autoIncrementingPrimaryKey != null && validatorDefinition.type instanceof ColumnDefinition.Type.Primary) {
                    processorManager.logError("You cannot mix and match autoincrementing and composite primary keys.");
                    success = false;
                }

                if (validatorDefinition.type instanceof ColumnDefinition.Type.PrimaryAutoIncrement
                        || validatorDefinition.type instanceof ColumnDefinition.Type.RowId) {
                    if (autoIncrementingPrimaryKey == null) {
                        autoIncrementingPrimaryKey = validatorDefinition;
                    } else if (autoIncrementingPrimaryKey != validatorDefinition) {
                        processorManager.logError("Only one auto-incrementing primary key is allowed on a table. " +
                                "Found Column: ${validatorDefinition.columnName} and type: ${validatorDefinition.elementTypeName}");
                        success = false;
                    }
                }
            }

            return success;
        }
    }

    /**
     * Description:
     */
    public static class ContentProviderValidator implements Validator<ContentProviderDefinition> {
        @Override
        public boolean validate(ProcessorManager processorManager, ContentProviderDefinition validatorDefinition) {
            boolean success = true;

            if (validatorDefinition.endpointDefinitions.isEmpty()) {
                processorManager.logError("The content provider ${validatorDefinition.element.simpleName} " +
                        "must have at least 1 @TableEndpoint associated with it");
                success = false;
            }

            return success;
        }
    }

    /**
     * Description:
     */
    public static class DatabaseValidator implements Validator<DatabaseDefinition> {
        @Override
        public boolean validate(ProcessorManager processorManager, DatabaseDefinition validatorDefinition) {
            return true;
        }
    }

    /**
     * Description:
     */
    public static class ModelViewValidator implements Validator<ModelViewDefinition> {
        @Override
        public boolean validate(ProcessorManager processorManager, ModelViewDefinition validatorDefinition) {
            return true;
        }
    }

    /**
     * Description: Validates to ensure a [OneToManyDefinition] is correctly coded. Will throw failures on the [ProcessorManager]
     */
    public static class OneToManyValidator implements Validator<OneToManyDefinition> {
        public boolean validate(ProcessorManager processorManager, OneToManyDefinition validatorDefinition) {
            return true;
        }
    }

    public static class TableEndpointValidator implements Validator<TableEndpointDefinition> {

        @Override
        public boolean validate(ProcessorManager processorManager, TableEndpointDefinition validatorDefinition) {
            boolean success = true;

            if (validatorDefinition.contentUriDefinitions.isEmpty()) {
                processorManager.logError("A table endpoint ${validatorDefinition.elementClassName} " +
                        "must supply at least one @ContentUri");
                success = false;
            }

            return success;
        }
    }

    /**
     * Description: Validates proper usage of the [com.dbflow5.annotation.Table]
     */
    public static class TableValidator implements Validator<TableDefinition> {

        @Override
        public boolean validate(ProcessorManager processorManager, TableDefinition validatorDefinition) {
            boolean success = true;

            if (!validatorDefinition.hasPrimaryConstructor) {
                processorManager.logError(TableValidator.class, "Table ${validatorDefinition.elementClassName}" +
                        " must provide a visible, parameterless constructor. Each field also must have a visible " +
                        "setter for now.");
                success = false;
            }

            if (validatorDefinition.columnDefinitions.isEmpty()) {
                processorManager.logError(TableValidator.class,
                        "Table ${validatorDefinition.associationalBehavior.name} " +
                                "of ${validatorDefinition.elementClassName}, ${validatorDefinition.element.javaClass} " +
                                "needs to define at least one column");
                success = false;
            }

            boolean hasTwoKinds = (validatorDefinition.primaryKeyColumnBehavior().hasAutoIncrement
                    || validatorDefinition.primaryKeyColumnBehavior().hasRowID)
                    && !validatorDefinition._primaryColumnDefinitions.isEmpty();

            if (hasTwoKinds) {
                processorManager.logError(TableValidator.class, "Table ${validatorDefinition.associationalBehavior.name}" +
                        " cannot mix and match autoincrement and composite primary keys");
                success = false;
            }

            boolean hasPrimary = (validatorDefinition.primaryKeyColumnBehavior().hasAutoIncrement
                    || validatorDefinition.primaryKeyColumnBehavior().hasRowID)
                    && validatorDefinition._primaryColumnDefinitions.isEmpty()
                    || !validatorDefinition.primaryKeyColumnBehavior().hasAutoIncrement
                    && !validatorDefinition.primaryKeyColumnBehavior().hasRowID
                    && !validatorDefinition._primaryColumnDefinitions.isEmpty();
            if (!hasPrimary && validatorDefinition.type == TableDefinition.Type.Normal) {
                processorManager.logError(TableValidator.class,
                        "Table ${validatorDefinition.associationalBehavior.name} " +
                                "needs to define at least one primary key");
                success = false;
            }

            return success;
        }
    }

    public static class TypeConverterValidator implements Validator<TypeConverterDefinition> {
        @Override
        public boolean validate(ProcessorManager processorManager, TypeConverterDefinition validatorDefinition) {
            boolean success = true;

            if (validatorDefinition.modelTypeName == null) {
                processorManager.logError("TypeConverter: ${validatorDefinition.className} uses an " +
                        "unsupported Model Element parameter. If it has type parameters, you must " +
                        "remove them or subclass it for proper usage.");
                success = false;
            } else if (validatorDefinition.dbTypeName == null) {
                processorManager.logError("TypeConverter: ${validatorDefinition.className} uses an " +
                        "unsupported DB Element parameter. If it has type parameters, you must remove" +
                        " them or subclass it for proper usage.");
                success = false;
            }

            return success;
        }
    }
}